Skip to content

Import interopRequireWildcard Babel helper from @babel/runtime instead of inlining it#57126

Open
ahmdshrif wants to merge 2 commits into
react:mainfrom
ahmdshrif:fix-babel-preset-interop-wildcard-helper
Open

Import interopRequireWildcard Babel helper from @babel/runtime instead of inlining it#57126
ahmdshrif wants to merge 2 commits into
react:mainfrom
ahmdshrif:fix-babel-preset-interop-wildcard-helper

Conversation

@ahmdshrif

@ahmdshrif ahmdshrif commented Jun 9, 2026

Copy link
Copy Markdown

Summary:

Fixes #57123.

@react-native/babel-preset enables @babel/plugin-transform-runtime with helpers: true but, unless a runtime version is explicitly passed via enableBabelRuntime, it doesn't set the plugin's version option — so it defaults to 7.0.0. @babel/plugin-transform-runtime only imports a helper from @babel/runtime if that helper existed at the configured version; anything added after 7.0.0 is inlined into every module instead.

The most visible victim is interopRequireWildcard, which Babel injects for every import * as X from '...' (e.g. import * as React from 'react'). Its modern (WeakMap-cached) form was added after 7.0.0, so today it is duplicated into every file that uses a namespace import — while sibling helpers like interopRequireDefault (unchanged since 7.0.0) are correctly imported once. This needlessly increases bundle size.

When a version isn't explicitly provided, this resolves the installed @babel/runtime version (require('@babel/runtime/package.json').version, with the same MODULE_NOT_FOUND try/catch + fallback used by babel-preset-expo) and passes it to transform-runtime, so all helpers available in that runtime — interopRequireWildcard and other post-7.0.0 helpers like callSuper/wrapRegExp — are imported from @babel/runtime and deduplicated instead of inlined.

Changelog:

[GENERAL] [FIXED] - Import the interopRequireWildcard Babel helper from @babel/runtime instead of inlining it into every module

Test Plan:

yarn jest packages/react-native-babel-preset50 passed.

  • Regenerated the transform snapshot fixtures; the diff is contained to helper deduplication (inlined interopRequireWildcard / createForOfIteratorHelperLoose / callSuper / wrapRegExp / etc. replaced with @babel/runtime imports — net fewer lines).
  • The existing "produces parseable JavaScript output" checks still pass for every profile.
  • Added a focused regression test asserting that import * as React results in an imported interopRequireWildcard (not an inlined function _interopRequireWildcard).

Before (per module):

var _react = _interopRequireWildcard(require("react"));
function _interopRequireWildcard(e, t) { /* …inlined in every file… */ }

After:

var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
var _react = _interopRequireWildcard(require("react"));

…nlining it

When no explicit runtime version was given, @react-native/babel-preset
enabled @babel/plugin-transform-runtime without a `version`, so it
defaulted to 7.0.0. Helpers added to @babel/runtime after 7.0.0 — most
notably the modern `interopRequireWildcard` used for every
`import * as X` — were therefore inlined into every module instead of
being imported once from @babel/runtime, needlessly bloating the bundle.

Default the transform-runtime `version` to 7.14.0 (the floor at which
`interopRequireWildcard` is available) when a version isn't explicitly
provided. The helper is now imported from @babel/runtime and deduplicated.

Fixes facebook#57123
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Jun 9, 2026
@facebook-github-tools facebook-github-tools Bot added the Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team. label Jun 9, 2026
@retyui

retyui commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

For a reference: babel/babel#18050

const runtimeVersion =
typeof options?.enableBabelRuntime === 'string'
? options.enableBabelRuntime
: '7.14.0';

@retyui retyui Jun 9, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call — done in c29d137. It now resolves the installed @babel/runtime version via require('@babel/runtime/package.json').version (with the same MODULE_NOT_FOUND try/catch + fallback as babel-preset-expo), so all helpers available in it are imported instead of only those up to a fixed floor. The snapshot fixtures now also dedupe callSuper, wrapRegExp, etc. Thanks for the reference!

Per review feedback: derive the transform-runtime version from the
installed @babel/runtime (matching babel-preset-expo) so all helpers
available in it are imported rather than only those up to a hardcoded
floor. Falls back to a conservative version when @babel/runtime can't
be resolved.
@retyui

retyui commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

@cortinico could you please review this PR ?

@cortinico

Copy link
Copy Markdown
Contributor

@cortinico could you please review this PR ?

cc @robhogan @huntie

@robhogan robhogan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working this and drawing attention to the problem. Fixing this would be a pretty useful win. FWIW, at Meta we use something similar to transform-runtime and hoist all used helpers up to props of a global, so implementations appear once per bundle without incurring module load cost at call sites.

The problem with the approach in to the PR is we've no (cheap, cache safe) way of knowing what version of @babel/runtime is installed for the app being built - effectively this change assumes it's at least as new as the one installed near @react-native/babel-preset but that isn't really justified. Multiple copies of @babel/runtime is very common especially on larger/mature projects, sadly.

I think we could bump the required version up to something modern and tag it as a breaking change (users might need to update), but the dynamic detection is a footgun for the reasons above, IMO.

helpers: true,
regenerator: enableRegenerator,
...(isVersion && {version: options.enableBabelRuntime}),
// Fall back to a conservative version when `@babel/runtime` can't be

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if there's a newer version of @babel/runtime resolvable from the preset than that resolvable from the source location?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

react-native-babel-preset doesn't import _interopRequireWildcard as bebel helper

4 participants